﻿namespace Sai.Core.Rules
{
    using System.Linq;
	using Sai.Core.Introspection;
    using System.Collections.Generic;

	/// <summary>
	/// Rule used to check methods in a class declaration. 
	/// </summary>
    public class CheckTypeMethods : IReferenceTypeRule
	{
		/// <summary>
		/// Validation for this rule. Feeds changes found to type difference object.
		/// </summary>
		/// <param name="was">The old version.</param>
		/// <param name="isNow">The new version.</param>
		/// <param name="typeDifference">The type difference.</param>
        public void Validate(ReferenceTypeDeclaration was, ReferenceTypeDeclaration isNow, ITypeDifference typeDifference)
		{
			if (was.Methods.Count == 0 && isNow.Methods.Count == 0)
			{
				return;
			}

            var obsolete = isNow.Methods.FindObsoleteMembers();

            foreach (var obs in obsolete)
            {
                typeDifference.IdentifiedChange(
                    new UnaryTypeChange(
                        TypeChangeReason.MethodObsolete,
                        obs,
                        obs.ObsoleteReason
                    ));
            }

            // consider non-overloaded methods first (easiest)
            var oldNonOverloadedMethods = was.NonOverloadedMethods;
            var newNonOverloadedMethods = isNow.NonOverloadedMethods;

            HashSet<UnaryTypeChange> identifiedChanges = new HashSet<UnaryTypeChange>();

            if (oldNonOverloadedMethods.Any() || newNonOverloadedMethods.Any())
            {
                var removed = newNonOverloadedMethods.FindRemovedMembers(oldNonOverloadedMethods);

                foreach (var remove in removed)
                {
                    identifiedChanges.Add(                     
                        new UnaryTypeChange(
                            TypeChangeReason.MethodRemoved,
                            remove
                        ));
                }

                var added = newNonOverloadedMethods.FindAddedMembers(oldNonOverloadedMethods);

                foreach (var add in added)
                {
                    if (add.IsAbstract)
                    {
                        identifiedChanges.Add(
                            new UnaryTypeChange(
                                TypeChangeReason.MethodAdded,
                                add,
                                "Abstract method added"
                            ));
                    }
                    else
                    {
                        identifiedChanges.Add(
                            new UnaryTypeChange(
                                TypeChangeReason.MethodAdded,
                                add
                            ));
                    }
                }

                // changed scope ?
                var commonMethods = newNonOverloadedMethods.FindMatchingMembers(oldNonOverloadedMethods);

                foreach (var commonMethod in commonMethods)
                {
                    MethodDeclaration oldMethod = commonMethod.Item1;
                    MethodDeclaration newMethod = commonMethod.Item2;

                    if ((oldMethod.IsProtected && !newMethod.IsProtected)
                        || (!oldMethod.IsProtected && newMethod.IsProtected))
                    {
                        typeDifference.IdentifiedChange(
                            new BinaryTypeChange(
                                TypeChangeReason.MethodChanged,
                                oldMethod,
                                newMethod
                                ));
                    }
                }
            }

            var oldOverloadedMethods = was.OverloadedMethods;
            var newOverloadedMethods = isNow.OverloadedMethods;

            if (oldOverloadedMethods.Any() || newOverloadedMethods.Any())
            {
                // for each method
                // does it have overloads ?
                // has it been removed
                // has it's return type changed
                // has it's signature changed

                // consider overloads
                // number of parameters changed

                // return type changed.

                // check method used to be virtual

                var oldMethods = was.Methods.RemoveExactMatches(isNow.Methods);
                var newMethods = isNow.Methods.RemoveExactMatches(was.Methods);

                foreach (var method in oldMethods)
                {
                    // removed
                    identifiedChanges.Add(
                            new UnaryTypeChange(
                                TypeChangeReason.MethodRemoved,
                                method
                            ));
                }

                foreach (var method in newMethods)
                {
                    if (method.IsAbstract)
                    {
                        identifiedChanges.Add(
                            new UnaryTypeChange(
                                TypeChangeReason.MethodAdded,
                                method,
                                "Abstract method added"
                            ));
                    }
                    else
                    {
                        identifiedChanges.Add(
                            new UnaryTypeChange(
                                TypeChangeReason.MethodAdded,
                                method
                            ));
                    }
                }
            }

            // now declare all unique changes.
            foreach (UnaryTypeChange change in identifiedChanges)
            {
                typeDifference.IdentifiedChange(change);
            }
		}
	}
}

